Skip to content

Conversation

@mmartinv
Copy link

@mmartinv mmartinv commented Oct 21, 2025

Refactor fdo.upload owner to use os.OpenRoot and prevent path traversal

Replace direct filesystem operations with os.OpenRoot-based security to
prevent path traversal attacks in the upload finalize function. The
previous implementation was vulnerable to malicious filenames that could
escape the upload directory using path traversal sequences.

Key changes:

  • Change UploadRequest.Rename from string to func(string) string for
    flexible filename transformation
  • Add UploadRequest.Overwrite flag to control file replacement behavior
  • Use os.OpenRoot to create a sandboxed filesystem rooted at u.Dir,
    preventing all operations from escaping the upload directory
  • Add platform-specific sameFilesystem() helpers to detect when temp
    file and destination are on the same filesystem (fs_unix.go,
    fs_windows.go, fs_other.go)
  • Use efficient os.Rename when on same filesystem, fall back to
    io.Copy for cross-filesystem moves
  • Auto-create parent directories with MkdirAll
  • Prevent overwriting existing files unless Overwrite flag is set

Closes #69

@mmartinv mmartinv force-pushed the fsim-upload-chroot-security branch 2 times, most recently from acd3205 to c49848a Compare October 21, 2025 11:22
Copy link
Member

@runcom runcom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@runcom
Copy link
Member

runcom commented Oct 21, 2025

@ben-krieger ptal

@ben-krieger
Copy link
Member

Is CreateTemp still needed? It was initially introduced, because os.Rename didn't work across filesystems (e.g. tmpfs to ext4).

@mmartinv
Copy link
Author

Is CreateTemp still needed? It was initially introduced, because os.Rename didn't work across filesystems (e.g. tmpfs to ext4).

The proposed changes always copy the contents from the temp file to the final destination so it will work across filesystems. However, always copying the contents from the temp file to the final destination is sub-optimal for temp files created in the same filesystem. I like the idea of creating temporary files to upload the content and only create the destination file if the upload was successful so I think the better approach here is to maintain the CreateTemp and detect if the temporary file is in the same filesystem as the destination file and if it is, call rename instead of copy. I will update the PR with the appropriate changes.

@mmartinv mmartinv force-pushed the fsim-upload-chroot-security branch from c49848a to 0fb3899 Compare October 23, 2025 11:16
@mmartinv mmartinv requested a review from runcom October 23, 2025 11:18
@mmartinv mmartinv force-pushed the fsim-upload-chroot-security branch 2 times, most recently from c1c38c8 to a7e2026 Compare October 23, 2025 12:10
@runcom
Copy link
Member

runcom commented Oct 30, 2025

@mmartinv can you rebase?

@ben-krieger can you take a look?

@mmartinv mmartinv force-pushed the fsim-upload-chroot-security branch from a7e2026 to 904702b Compare October 30, 2025 11:23
return false, false, fmt.Errorf("path traversal detected in rename: %q", u.Rename)
}

// Backup existing file if present
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is potentially nice behavior not to clobber the file, but it's also unconfigurable and undocumented.

Following the principle of least surprise, I think you either overwrite or fail, not backup-and-overwrite.

I believe that the best API decision to reduce user error would be fail-if-existing with an optional configurable function for what to do on conflict.

Another clear API win would be to change Rename from a string to a func(string) string so that users can make every upload timestamped by upload time.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I basically agree on everything so I did the following changes:

  • I changed Rename from a string to a func(string) string so now it's possible to define a function to rename the uploaded files.
  • Fail if the uploaded file already exists and define the Overwrite variable (default to false) to change the behavior if desired.

Apart from that I also changed a bit the behavior of how files are uploaded: Now if the uploads dir is /var/lib/uploads and the file uploaded from the device is /etc/hosts any parent dir will be created relative to the uploads dir and the file will be uploaded to /var/lib/uploads/etc/hosts. I thought that this change alongside #173 will allow us to make per device independent uploads /var/lib/uploads/${device_cert_serial_id}/etc/hosts. If you think there's a better approach just say it and I will try to implement it :)

Replace direct filesystem operations with os.OpenRoot-based security to
prevent path traversal attacks in the upload finalize function. The
previous implementation was vulnerable to malicious filenames that could
escape the upload directory using path traversal sequences.

Key changes:
- Change UploadRequest.Rename from string to func(string) string for
  flexible filename transformation
- Add UploadRequest.Overwrite flag to control file replacement behavior
- Use os.OpenRoot to create a sandboxed filesystem rooted at u.Dir,
  preventing all operations from escaping the upload directory
- Add platform-specific sameFilesystem() helpers to detect when temp
  file and destination are on the same filesystem (fs_unix.go,
  fs_windows.go, fs_other.go)
- Use efficient os.Rename when on same filesystem, fall back to
  io.Copy for cross-filesystem moves
- Auto-create parent directories with MkdirAll
- Prevent overwriting existing files unless Overwrite flag is set

Closes fido-device-onboard#69

Signed-off-by: Miguel Martín <[email protected]>
Add comprehensive test coverage for the os.OpenRoot-based security
implementation in the fdo.upload owner module:

- TestUploadRequestPathTraversalPrevention: Verifies multiple path
  traversal attack vectors (../, absolute paths, embedded sequences)
  are blocked by os.OpenRoot
- TestUploadRequestNormalFlow: Validates standard upload with proper
  file placement and content verification
- TestUploadRequestDefaultRename: Confirms default Rename function
  strips leading slashes from device-provided paths
- TestUploadRequestSHA384Mismatch: Ensures upload fails when SHA-384
  checksum validation fails
- TestUploadRequestSameFilesystemRename: Verifies rename optimization
  when temp and destination are on same filesystem

Also updates existing tests to use Overwrite flag and octal literals.

Related to fido-device-onboard#69

Signed-off-by: Miguel Martín <[email protected]>
@mmartinv mmartinv force-pushed the fsim-upload-chroot-security branch from 150ea7f to bf4a470 Compare December 19, 2025 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enforce chroot-like security in fdo.upload owner FSIM using [os.Root](https://tip.golang.org/pkg/os#Root)

3 participants